1
1
.
.
4
4
.
.
5
5
S
S
e
e
c
c
u
u
r
r
i
i
t
t
y
y
E
E
x
x
p
p
r
r
e
e
s
s
s
s
i
i
o
o
n
n
s
s
-
-
@
@
P
P
r
r
e
e
A
A
u
u
t
t
h
h
o
o
r
r
i
i
z
z
e
e
-
-
C
C
u
u
s
s
t
t
o
o
m
m
M
M
e
e
t
t
h
h
o
o
d
d
s
s
-
-
B
B
o
o
o
o
k
k
s
s
I
I
n
n
f
f
o
o
[
[
G
G
]
]
This tutorial shows how to use Custom Method to Authorize access to Endpoint.
We will create Custom Class AuthenticationService.java with Method authenticate() that returns
true if Authenticated User owns the Book it wants to read
false if Authenticated User doesn't own the Book it wants to read
User will call http://localhost:8080/ReadBook/1 where 1 represents bookId.
We will load DB with few Books (as shown below).
MyController.java
@PreAuthorize("hasAuthority('book.read') AND @authenticationService.authenticate(authentication)")
Application Schema [Results]
DB Table: Book (preloaded data)
ID
TITLE
USERNAME
1
Book about dogs
john
2
Book about cats
bill
Spring Boot Starters
GROUP
DEPENDENCY
DESCRIPTION
Web
Spring Web
Enables @Controller, @RequestMapping and Tomcat Server
Security
Spring Security
Enables Spring Security
SQL
Spring Data JPA
Enables @Entity and @Id
SQL
H2 Database
Enables in-memory H2 Database
AuthenticationService
http://localhost:8080/ReadBook
/1
readBook()
AccountService
MyController
Browser
Tomcat
Book
BookRepository
P
P
r
r
o
o
c
c
e
e
d
d
u
u
r
r
e
e
Create Project: springboot_security_authorization_custom_books (add Spring Boot Starters from the table)
Edit File: application.properties (specify Username, Password, Role)
Create Package: entities (inside package main)
Create Class: PersonEntity.java (inside package entities)
Create Package: repositories (inside package main)
Create Interface: PersonRepository.java (inside package repositories)
Create Package: config (inside package main)
Create Class: BookLoader.java (inside package config)
Create Class: SecurityConfig.java (inside package config)
Create Package: services (inside package main)
Create Class: AccountService.java (inside package services)
Create Class: AuthenticationService.java (inside package services)
Create Package: controllers (inside package main)
Create Class: MyController.java (inside package controllers)
application.properties
# SECURITY
spring.security.user.name = john
spring.security.user.password = mypassword
spring.security.user.profile = USER
# CRUD AUTHORITIES
profile.user = book.read
profile.admin = book.create, book.read, book.update, book.delete
# H2 DATABASE
spring.h2.console.enabled = true
spring.datasource.url = jdbc:h2:mem:testdb
Book.java
package com.ivoronline.springboot_security_authorization_custom_books.entities;
import org.springframework.stereotype.Component;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Component
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer id;
public String userName;
public String title;
}
BookRepository.java
package com.ivoronline.springboot_security_authorization_custom_books.repositories;
import com.ivoronline.springboot_security_authorization_custom_books.entities.Book;
import org.springframework.data.repository.CrudRepository;
public interface BookRepository extends CrudRepository<Book, Integer> { }
BookLoader.java
package com.ivoronline.springboot_security_authorization_custom_books.config;
import com.ivoronline.springboot_security_authorization_custom_books.entities.Book;
import com.ivoronline.springboot_security_authorization_custom_books.repositories.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class BookLoader implements CommandLineRunner {
@Autowired
private BookRepository bookRepository;
@Override
@Transactional
public void run(String... args) throws Exception {
//BBOK1
Book book1 = new Book();
book1.title = "Book about dogs";
book1.userName = "john";
//BBOK2
Book book2 = new Book();
book2.title = "Book about cats";
book2.userName = "bill";
//STORE ACCOUNT INTO DB
bookRepository.save(book1);
bookRepository.save(book2);
}
}
MyController.java
package com.ivoronline.springboot_security_authorization_custom_books.controllers;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class MyController {
@ResponseBody
@RequestMapping("/ReadBook/{BookId}")
@PreAuthorize("hasAuthority('book.read') AND @authenticationService.authenticate(authentication, #bookId)")
public String readBook(@PathVariable("BookId") String bookId) {
return "USER can read his Book";
}
}
AuthenticationService.java
package com.ivoronline.springboot_security_authorization_custom_books.services;
import com.ivoronline.springboot_security_authorization_custom_books.entities.Book;
import com.ivoronline.springboot_security_authorization_custom_books.repositories.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class AuthenticationService {
@Autowired
BookRepository bookRepository;
@Transactional
public boolean authenticate(Authentication authentication, Integer bookId) {
//GET BOOK
Book book = bookRepository.findById(bookId).get();
//GET BOOK USERNAME
String bookUserName = book.userName;
//GET AUTHENTICATED USERNAME
UserDetails user = (UserDetails) authentication.getPrincipal();
String authenticatedUserName = user.getUsername();
//CHECK OWNERSHIP
if (bookUserName.equals(authenticatedUserName)) { return true; }
else { return false; }
}
}
SecurityConfig.java
package com.ivoronline.springboot_security_authorization_custom_books.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//H2 CONSOLE
httpSecurity.authorizeRequests(authorize -> { authorize.antMatchers("/h2-console/**").permitAll(); });
httpSecurity.headers().frameOptions().sameOrigin();
httpSecurity.csrf().disable();
//EVERYTHING ELSE IS LOCKED
httpSecurity.formLogin();
}
}
AccountService.java
package com.ivoronline.springboot_security_authorization_custom_books.services;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
public class AccountService implements UserDetailsService {
//LOAD PROPERTIES (from application.properties file)
@Value("${spring.security.user.name}") private String userName;
@Value("${spring.security.user.password}") private String userPassword;
@Value("${spring.security.user.profile}") private String userProfile;
@Value("${profile.user}") private String profileUser;
@Value("${profile.admin}") private String profileAdmin;
@Override
public UserDetails loadUserByUsername(String enteredUserName) throws UsernameNotFoundException {
//CHECK USERNAME
if(!enteredUserName.equals(userName)) { throw new UsernameNotFoundException("User not found"); }
//GET AUTHORITIES FOR GIVEN USER PROFILE
String userAuthorities = "";
if(userProfile.equals("USER") ) { userAuthorities = profileUser; }
if(userProfile.equals("ADMIN")) { userAuthorities = profileAdmin; }
//GET AUTHORITIES FROM STRING PROPERTY
String[] authoritiesArray = userAuthorities.split(", ");
List<String> authoritiesList = Arrays.asList(authoritiesArray);
//CREATE AUTHORITIES (FOR USER OBJECT)
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String authority : authoritiesList) {
authorities.add(new SimpleGrantedAuthority(authority.trim()));
}
//CREATE USER
User user = new User(userName, userPassword, true, true, true, true, authorities);
//RETURN USER
return user;
}
}
R
R
e
e
s
s
u
u
l
l
t
t
s
s
http://localhost:8080/ReadBook/1
@PreAuthorize("hasAuthority('book.read') AND @authenticationService.authenticate(authentication)")
http://localhost:8080/h2-console
Application Structure
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>